#pragma once

#include <vex/core/config.h>
#include <vex/core/make_raw_handle.h>
#include <vex/core/as_any.h>
#include <vex/iterable/stl_interop.h>
#include <vex/functional/contract/multicast_delegate.h>
#include <vex/functional/contract/invoker_range.h>
#include <vex/functional/result.h>
#include <vex/functional/replace_result.h>
#include <vex/functional/implementation/default_value_reducer.h>
#include <vex/functional/any_delegate.h>
#include <utility>
#include <type_traits>
#include <vector>
#include <functional>
#include <algorithm>
#include <tuple>

namespace vex { namespace functional { namespace implementation {

    template< 
        class TSignature,
        class TReducer = default_value_reducer<typename result<TSignature>::type>,
        class TMulticastDelegateContract = contract::multicast_delegate<
            TSignature,
            typename std::result_of<
                TReducer(contract::invoker_range<typename result<TSignature>::type>*)
            >::type
        > 
    >
    class multicast_delegate_default_impl;

    namespace detail {
        template<
            class TSignature, 
            class TReducer,
            class TMulticastDelegateContract
        >
        class VEX_ABSTRACT multicast_delegate_default_impl_base : public TMulticastDelegateContract {
            multicast_delegate_default_impl_base(multicast_delegate_default_impl_base const&);
            multicast_delegate_default_impl_base& operator=(multicast_delegate_default_impl_base const&);
        protected:
            typedef typename TMulticastDelegateContract::delegate_type delegate_type;
            typedef typename TMulticastDelegateContract::invocation_list_type invocation_list_type;
            typedef typename TMulticastDelegateContract::result_type result_type;
            typedef typename std::remove_reference<TReducer>::type reducer_type;
            typedef std::vector< any_delegate<TSignature> > inv_list_impl_type;
            typedef typename inv_list_impl_type::iterator inv_list_impl_itr_type;
            typedef multicast_delegate_default_impl<TSignature, TReducer, TMulticastDelegateContract> final_type;

            reducer_type m_reducer;
            inv_list_impl_type m_inv_list;

            template<class UReducer> 
            multicast_delegate_default_impl_base(UReducer&& p_reducer)
                : m_reducer(std::forward<UReducer>(p_reducer)) {}

            template<class UReducer>
            multicast_delegate_default_impl_base(
                UReducer&& p_reducer,
                inv_list_impl_type& p_inv_list)
                : m_reducer(std::forward<UReducer>(p_reducer))
                , m_inv_list(std::move(p_inv_list)) {}

            multicast_delegate_default_impl_base(multicast_delegate_default_impl_base&& p_other)
                : m_reducer(std::move(p_other.m_reducer))
                , m_inv_list(std::move(p_other.m_inv_list)) {}

            /// <summary>
            /// Implements invocation_list_type of multicats delegate contract. Will be returned as
            /// result of <c>get_invocation_list</c> query
            /// </summary>
            class VEX_ABSTRACT invocation_list_range_impl : public invocation_list_type {
                inv_list_impl_type m_inv_list;
                typedef iterable::stl_interop::range_from_iterator<inv_list_impl_itr_type> range_type;
                range_type m_range;
            protected:
                bool move_front()
                {
                    return m_range.move_front();
                }

                delegate_type* front() const
                {
                    return vex::as_raw_out(m_range.front());
                }

                invocation_list_range_impl(inv_list_impl_type const & p_inv_list)
                    : m_inv_list(p_inv_list)
                    , m_range(vex::iterable::make_range(m_inv_list)) {}

                invocation_list_range_impl(inv_list_impl_type&& p_inv_list)
                    : m_inv_list(std::move(p_inv_list))
                    , m_range(vex::iterable::make_range(m_inv_list)) {}
            };

            /// auxiliary internal methods region starts here:

            /// <summary>
            /// internal implementation of "combine" with one delegate
            /// </summary>
            bool internal_combine(
                delegate_type* p_delegate,
                inv_list_impl_type& p_new_inv_list) const
            {
                if (p_delegate) {
                    try {
                        p_new_inv_list = this->m_inv_list;
                        p_new_inv_list.push_back(vex::as_any_from_input() | p_delegate);
                        return true;
                    } catch (std::exception&) {}
                }
                return false;
            }

            bool internal_combine(
                invocation_list_type* p_delegate_list,
                inv_list_impl_type& p_new_inv_list) const
            {
                if (p_delegate_list) {
                    try {
                        p_new_inv_list = this->m_inv_list;
                        while (p_delegate_list->move_front()) {
                            p_new_inv_list.push_back(vex::as_any_from_output() | p_delegate_list->front());
                        }
                        return true;
                    } catch (std::exception&) {}
                }
                return false;
            }

            /// <summary>
            /// exctract delegates from invocation list contract
            /// </summary>
            static void internal_to_inv_list_rep(
                invocation_list_type* p_inv_list, 
                inv_list_impl_type& p_internal_inv_list)
            {
                while (p_inv_list->move_front()) {
                    p_internal_inv_list.push_back(vex::as_any_from_output() | p_inv_list->front());
                }
            }

            /// <summary>
            /// Removes range of delegates <paramref name="p_range_to_remove"/> from <paramref name="p_source_range"/>
            /// </summary>
            ///
            /// <returns>true if occurrence found, false otherwise</returns>
            static bool internal_erase_last_occurence(
                inv_list_impl_type& p_source_range,
                inv_list_impl_type const& p_range_to_remove)
            {
                // will use default comparison operator
                auto t_last_occurence_beg = std::find_end(
                    p_source_range.begin(), p_source_range.end(),
                    p_range_to_remove.begin(), p_range_to_remove.end());
                if (t_last_occurence_beg != p_source_range.end()) {
                    p_source_range.erase(
                        t_last_occurence_beg, 
                        t_last_occurence_beg + p_range_to_remove.size());
                    return true;
                } else {
                    return false;
                }
            }

            bool internal_remove(
                delegate_type* p_delegate,
                removal_mode::domain p_mode,
                inv_list_impl_type& p_new_inv_list) const
            {
                if (p_delegate) {
                    if (!this->m_inv_list.empty()) try {
                        p_new_inv_list = this->m_inv_list;
                        if (p_mode == removal_mode::last) {
                            auto t_itr = std::find(
                                p_new_inv_list.rbegin(), p_new_inv_list.rend(), 
                                vex::as_any_from_in(p_delegate));

                            if (t_itr != p_new_inv_list.rend()) {
                                auto t_itr_base = (++t_itr).base();
                                p_new_inv_list.erase(t_itr_base);
                            }
                        } else {
                            p_new_inv_list.erase(
                                std::remove(p_new_inv_list.begin(), p_new_inv_list.end(), vex::as_any_from_in(p_delegate)), 
                                p_new_inv_list.end());
                        }

                        return (p_new_inv_list.size() != this->m_inv_list.size());
                    } catch (std::exception&) {}
                }

                return false;
            }

            bool internal_remove(
                invocation_list_type* p_delegate_list,
                removal_mode::domain p_mode,
                inv_list_impl_type& p_new_inv_list) const
            {
                if (p_delegate_list) {
                    if (!this->m_inv_list.empty()) try {
                        inv_list_impl_type t_delegate_list;
                        internal_to_inv_list_rep(p_delegate_list, t_delegate_list);

                        if (!t_delegate_list.empty()) {
                            p_new_inv_list = this->m_inv_list;

                            switch (p_mode) {
                            case removal_mode::last:
                                internal_erase_last_occurence(p_new_inv_list, t_delegate_list);
                                break;
                            case removal_mode::all:
                                while (internal_erase_last_occurence(p_new_inv_list, t_delegate_list));
                                break;
                            default:
                                break;
                            }

                            return p_new_inv_list.size() != this->m_inv_list.size();
                        }
                    } catch (...) {}
                }

                return false;
            }

            final_type* create_new(inv_list_impl_type& p_new_inv_list) const
            {
                auto t_reducer = m_reducer;
                return core::make_raw_handle<final_type>(std::move(t_reducer), p_new_inv_list);
            }
        public:
            // implements combine with one delegate
            virtual TMulticastDelegateContract* combine(
                delegate_type* p_delegate) const
            {
                inv_list_impl_type t_new_inv_list;
                return internal_combine(p_delegate, t_new_inv_list) ?
                    create_new(t_new_inv_list) : nullptr;
            }

            // implements combine with invocation list
            virtual TMulticastDelegateContract* combine(
                invocation_list_type* p_delegate_list) const
            {
                inv_list_impl_type t_new_inv_list;
                return internal_combine(p_delegate_list, t_new_inv_list) ?
                    create_new(t_new_inv_list) : nullptr;
            }

            // implements remove
            virtual TMulticastDelegateContract* remove(
                delegate_type* p_delegate,
                removal_mode::domain p_mode = removal_mode::last) const
            {
                inv_list_impl_type t_new_inv_list;
                return internal_remove(p_delegate, p_mode, t_new_inv_list) ?
                    create_new(t_new_inv_list) : nullptr;
            }

            // implements remove with invocation_list
            virtual TMulticastDelegateContract* remove(
                invocation_list_type* p_delegate_list,
                removal_mode::domain p_mode = removal_mode::last) const
            {
                inv_list_impl_type t_new_inv_list;
                return internal_remove(p_delegate_list, p_mode, t_new_inv_list) ?
                    create_new(t_new_inv_list) : nullptr;
            }

            /// Implements get_invocation_list. Creating a copy of current invocation list
            /// decouples the returned instance from the lifetime of this instance.
            virtual invocation_list_type* get_invocation_list() const
            {
                try {
                    inv_list_impl_type t_new_inv_list = m_inv_list;
                    return core::make_raw_handle<invocation_list_range_impl>(std::move(t_new_inv_list));
                } catch (std::exception&) {
                    return nullptr;
                }
            }
        };

        struct nothing {};

        template <class TRet, class TInvRange, class TArgs>
        class invoker_range_impl : public contract::invoker_range<TRet> 
        {
            TInvRange m_inv_range;
            mutable TArgs m_args;

            typedef typename TRet result_type;
            typedef typename vex::iterable::range_traits<TInvRange>::value_type delegate_type;

            result_type invoke(
                delegate_type const & p_delegate,
                nothing const &) const
            {
                return p_delegate->invoke();
            }

            template<class TArg1>
            result_type invoke(
                delegate_type const & p_delegate,
                std::tuple<TArg1> & p_args) const
            {
                return p_delegate->invoke(
                    std::forward<TArg1>(std::get<0>(p_args)));
            }

            template<class TArg1, class TArg2>
            result_type invoke(
                delegate_type const & p_delegate,
                std::tuple<TArg1, TArg2> & p_args) const
            {
                return p_delegate->invoke(
                    std::forward<TArg1>(std::get<0>(p_args)),
                    std::forward<TArg2>(std::get<1>(p_args)));
            }

            template<class TArg1, class TArg2, class TArg3>
            result_type invoke(
                delegate_type const & p_delegate,
                std::tuple<TArg1, TArg2, TArg3> & p_args) const
            {
                return p_delegate->invoke(
                    std::forward<TArg1>(std::get<0>(p_args)),
                    std::forward<TArg2>(std::get<1>(p_args)),
                    std::forward<TArg3>(std::get<2>(p_args)));
            }

            template<class TArg1, class TArg2, class TArg3, class TArg4>
            result_type invoke(
                delegate_type const & p_delegate,
                std::tuple<TArg1, TArg2, TArg3, TArg4> & p_args) const
            {
                return p_delegate->invoke(
                    std::forward<TArg1>(std::get<0>(p_args)),
                    std::forward<TArg2>(std::get<1>(p_args)),
                    std::forward<TArg3>(std::get<2>(p_args)),
                    std::forward<TArg4>(std::get<3>(p_args)));
            }

            template<class TArg1, class TArg2, class TArg3, class TArg4, class TArg5>
            result_type invoke(
                delegate_type const & p_delegate,
                std::tuple<TArg1, TArg2, TArg3, TArg4, TArg5> & p_args) const
            {
                return p_delegate->invoke(
                    std::forward<TArg1>(std::get<0>(p_args)),
                    std::forward<TArg2>(std::get<1>(p_args)),
                    std::forward<TArg3>(std::get<2>(p_args)),
                    std::forward<TArg4>(std::get<3>(p_args)),
                    std::forward<TArg5>(std::get<4>(p_args)));
            }

            virtual void add_ref() {}
            virtual void release() {}
            virtual bool has_synchronized_ref_count() const { return false; }
            virtual bool is_shareable() const { return false; }
        public:
            virtual bool move_front()
            {
                return m_inv_range.move_front();
            }

            virtual result_type front() const
            {
                return invoke(m_inv_range.front(), m_args);
            }

            template<class UInvRange, class UArgs>
            invoker_range_impl(UInvRange&& p_inv_range, UArgs&& p_args) 
                : m_inv_range(std::forward<UInvRange>(p_inv_range))
                , m_args(std::forward<UArgs>(p_args)) {}
        };

        template<class TRet, class TInvList, class TArgs> 
        auto make_invoker_range(TInvList const & p_inv_list, TArgs&& p_args)
            -> invoker_range_impl<
                TRet, 
                decltype(iterable::make_range(p_inv_list.cbegin(), p_inv_list.cend())),
                TArgs
            >
        {
            return invoker_range_impl<
                TRet, 
                decltype(iterable::make_range(p_inv_list.cbegin(), p_inv_list.cend())),
                TArgs
            >(
                iterable::make_range(p_inv_list.cbegin(), p_inv_list.cend()), 
                std::forward<TArgs>(p_args));
        };
    }

#define VEX_MULTICAST_DELEGATE_DEFAULT_IMPL_BODY(DelegateImplClassName) \
    typedef typename base_type::delegate_type delegate_type; \
    typedef typename base_type::invocation_list_type invocation_list_type; \
    typedef typename base_type::result_type result_type; \
    typedef typename base_type::reducer_type reducer_type; \
    typedef typename base_type::inv_list_impl_type inv_list_impl_type; \
    \
    template<class UReducer> \
    DelegateImplClassName(UReducer&& p_reducer) \
        : base_type(std::forward<UReducer>(p_reducer)) {} \
    \
    template<class UReducer> \
    DelegateImplClassName( \
        UReducer&& p_reducer, \
        inv_list_impl_type& p_inv_list) \
    : base_type(std::forward<UReducer>(p_reducer), p_inv_list) {} \
    \
    DelegateImplClassName(multicast_delegate_default_impl&& p_other) \
        : base_type(static_cast<base_type&&>(p_other)) {} \
    \
    ~DelegateImplClassName() {}

    template <class TRet, class TReducer, class TMCDContract>
    class multicast_delegate_default_impl<TRet (), TReducer, TMCDContract>
        : public detail::multicast_delegate_default_impl_base<TRet (), TReducer, TMCDContract>
    {
    protected:
        typedef detail::multicast_delegate_default_impl_base<
            TRet (), 
            TReducer, 
            TMCDContract
        > base_type;

        VEX_MULTICAST_DELEGATE_DEFAULT_IMPL_BODY(multicast_delegate_default_impl);
    public:
        result_type invoke() const
        {
            auto t_invoker_range = detail::make_invoker_range<TRet>(
                this->m_inv_list,
                detail::nothing());
            auto t_reducer = this->m_reducer;
            return t_reducer(&t_invoker_range);
        }
    };

    template <
        class TRet, class TArg1, 
        class TReducer, 
        class TMCDContract
    >
    class multicast_delegate_default_impl<TRet (TArg1), TReducer, TMCDContract>
        : public detail::multicast_delegate_default_impl_base<TRet (TArg1), TReducer, TMCDContract>
    {
    protected:
        typedef detail::multicast_delegate_default_impl_base<
            TRet (TArg1), 
            TReducer, 
            TMCDContract
        > base_type;

        VEX_MULTICAST_DELEGATE_DEFAULT_IMPL_BODY(multicast_delegate_default_impl);
    public:
        result_type invoke(TArg1 p_arg1) const
        {
            auto t_invoker_range = detail::make_invoker_range<TRet>(
                this->m_inv_list,
                std::make_tuple(std::ref(p_arg1)));
            auto t_reducer = this->m_reducer;
            return t_reducer(&t_invoker_range);
        }
    };

    template <
        class TRet, class TArg1, class TArg2,
        class TReducer, 
        class TMCDContract
    >
    class multicast_delegate_default_impl<TRet (TArg1, TArg2), TReducer, TMCDContract>
        : public detail::multicast_delegate_default_impl_base<TRet (TArg1, TArg2), TReducer, TMCDContract>
    {
    protected:
        typedef detail::multicast_delegate_default_impl_base<
            TRet (TArg1, TArg2), 
            TReducer, 
            TMCDContract
        > base_type;

        VEX_MULTICAST_DELEGATE_DEFAULT_IMPL_BODY(multicast_delegate_default_impl);
    public:
        result_type invoke(TArg1 p_arg1, TArg2 p_arg2) const
        {
            auto t_invoker_range = detail::make_invoker_range<TRet>(
                this->m_inv_list,
                std::make_tuple(
                std::ref(p_arg1), 
                std::ref(p_arg2)));
            auto t_reducer = this->m_reducer;
            return t_reducer(&t_invoker_range);
        }
    };

    template <
        class TRet, class TArg1, class TArg2, class TArg3,
        class TReducer, 
        class TMCDContract
    >
    class multicast_delegate_default_impl<TRet (TArg1, TArg2, TArg3), TReducer, TMCDContract>
        : public detail::multicast_delegate_default_impl_base<TRet (TArg1, TArg2, TArg3), TReducer, TMCDContract>
    {
    protected:
        typedef detail::multicast_delegate_default_impl_base<
            TRet (TArg1, TArg2, TArg3), 
            TReducer, 
            TMCDContract
        > base_type;

        VEX_MULTICAST_DELEGATE_DEFAULT_IMPL_BODY(multicast_delegate_default_impl);
    public:
        result_type invoke(TArg1 p_arg1, TArg2 p_arg2, TArg3 p_arg3) const
        {
            auto t_invoker_range = detail::make_invoker_range<TRet>(
                this->m_inv_list,
                std::make_tuple(
                std::ref(p_arg1), 
                std::ref(p_arg2),
                std::ref(p_arg3)));
            auto t_reducer = this->m_reducer;
            return t_reducer(&t_invoker_range);
        }
    };

    template <
        class TRet, class TArg1, class TArg2, class TArg3, class TArg4,
        class TReducer, 
        class TMCDContract
    >
    class multicast_delegate_default_impl<TRet (TArg1, TArg2, TArg3, TArg4), TReducer, TMCDContract>
        : public detail::multicast_delegate_default_impl_base<TRet (TArg1, TArg2, TArg3, TArg4), TReducer, TMCDContract>
    {
    protected:
        typedef detail::multicast_delegate_default_impl_base<
            TRet (TArg1, TArg2, TArg3, TArg4), 
            TReducer, 
            TMCDContract
        > base_type;

        VEX_MULTICAST_DELEGATE_DEFAULT_IMPL_BODY(multicast_delegate_default_impl);
    public:
        result_type invoke(TArg1 p_arg1, TArg2 p_arg2, TArg3 p_arg3, TArg4 p_arg4) const
        {
            auto t_invoker_range = detail::make_invoker_range<TRet>(
                this->m_inv_list,
                std::make_tuple(
                std::ref(p_arg1), 
                std::ref(p_arg2),
                std::ref(p_arg3),
                std::ref(p_arg4)));
            auto t_reducer = this->m_reducer;
            return t_reducer(&t_invoker_range);
        }
    };

    template <
        class TRet, class TArg1, class TArg2, class TArg3, class TArg4, class TArg5,
        class TReducer, 
        class TMCDContract
    >
    class multicast_delegate_default_impl<TRet (TArg1, TArg2, TArg3, TArg4, TArg5), TReducer, TMCDContract>
        : public detail::multicast_delegate_default_impl_base<TRet (TArg1, TArg2, TArg3, TArg4, TArg5), TReducer, TMCDContract>
    {
    protected:
        typedef detail::multicast_delegate_default_impl_base<
            TRet (TArg1, TArg2, TArg3, TArg4, TArg5), 
            TReducer, 
            TMCDContract
        > base_type;

        VEX_MULTICAST_DELEGATE_DEFAULT_IMPL_BODY(multicast_delegate_default_impl);
    public:
        result_type invoke(TArg1 p_arg1, TArg2 p_arg2, TArg3 p_arg3, TArg4 p_arg4, TArg5 p_arg5) const
        {
            auto t_invoker_range = detail::make_invoker_range<TRet>(
                this->m_inv_list,
                std::make_tuple(
                std::ref(p_arg1), 
                std::ref(p_arg2),
                std::ref(p_arg3),
                std::ref(p_arg4),
                std::ref(p_arg5)));
            auto t_reducer = this->m_reducer;
            return t_reducer(&t_invoker_range);
        }
    };
}}}